#include <boot/boot.h>
#include <drive/disk.h>
#include <asm/asm.h>
#include <boot/mbr.h>
#include <macros.h>


.section .text

    .global bootloader
    .code16
.text 1
bootloader:

# 为加载GDT32和GDT64做准备
    // movl $LOAD_CS_BASE, %esi
    // shl $4, %esi
    // orl %esi, (BaseAddr)
    // orl %esi, (BaseAddr64)

# 初始化打印函数
    call init_print

# 进入Big Real Mode
    // call enter_big_real_mode
    // pushw $0x0a
    // pushl $big_real_mode_msg_len
    // pushw $big_real_mode_msg
    // call print_msg
    // add $8, %sp

# 检查是否支持cupid
    call check_cpuid
    jc 80f # 80<<<
    pushw $0x0a
    pushl $supported_cupid_msg_len
    pushl $supported_cupid_msg
    call print_msg
    add $10, %sp

# 获取硬盘信息
    call get_disk_info
    cmp  $0, %eax
    jnz 82f # 82<<<
    pushw $0x0a
    pushl $get_disk_info_msg_len
    pushl $get_disk_info_msg
    call print_msg
    add $10, %sp


# 判断是否是lba48 暂时不考虑28,去掉
    call check_disk_lba48
    cmp  $1, %ax
    jnz 88f # 88<<<
    pushw $0x0a
    pushl $supported_lba48_msg_len
    pushl $supported_lba48_msg
    call print_msg
    add $10, %sp


 # 获取内存结构
    call get_memory_info
    cmp $0, %eax
    jnz 81f # 81<<<
    pushw $0x0a
    pushl $memory_info_msg_len
    pushl $memory_info_msg
    call print_msg
    add $10, %sp

# 寻找启动分区并找到Enjoy.bin文件，加载内存
# 拷贝Dos的DBR到DosDBR结构体中
    movl    $MBR_BASE_ADDR, %esi         # ESI 作为源地址.
    movl    $DosDBR, %edi                # EDI 作为目标地址.
    movl    $128, %ecx                   # 拷贝字节数.
11: # >>>11
    movl    (%esi), %eax                  # CX自减 128*4
    movl    %eax, (%edi)
    addl    $4, %esi
    addl    $4, %edi
    decl    %ecx
    jg 11b # 11<<<

#if 0
    nop # 获取最大寻址扇区数, edx高32, eax低32
    call get_max_offset_lba48
    nop
#endif

# 计算 fat1_offset=RsvdSecCnt*BytesPerClus    FAT1偏移字节数
#     cluster_size=SecPerClus*BytesPerClus   每个簇占用多少个字节
#     root_clus_offset=fat1_offset+NumFATs*FATSz32*BytesPerClus 根目录偏移字节数

    movw RsvdSecCnt(,1), %ax
    mulw BytesPerClus(,1)
    shl $16, %edx
    mov %ax, %dx
    movl %edx, fat1_offset(,1)
 
    movb $0, %ah
    movb SecPerClus(,1),%al
    mulw BytesPerClus(,1)
    shl $16, %edx
    mov %ax, %dx
    movl %edx, cluster_size(,1)

    movb $0, %ah
    movb NumFATs(,1),%al
    mulw BytesPerClus(,1)
    shl $16, %edx
    mov %ax, %dx
    movl %edx, %eax
    mull FATSz32(,1) # TODO: 不能超过4G，所以只取低字节,超过4G会出问题
    movl fat1_offset(,1), %ebx
    addl %ebx, %eax
    movl %eax, root_clus_offset(,1)

# <<<<<依次读取根目录的每一个扇区内容，偏移位置（RsvdSecCnt+NumFATs*FATSz32）*BytesPerClus，直到找到Enjoy.bin>>>>>
    movl $0, %ebx
    movw BytesPerClus(,1), %bx
    movl root_clus_offset(,1), %eax
    divl %ebx

#开始读取根目录簇(2号簇)的1个簇至 ROOT_CLUS_MEM_OFFSET
    xorw %bx, %bx
    movb SecPerClus(,1), %bl
    pushw %bx #1M/512=2048 个扇区
    pushl %eax
    pushl $0
    pushl $ROOT_CLUS_MEM_OFFSET
    call load_system_to_memory48
    add $14, %sp
    cmp $0, %eax
    jnz 89f # 89<<<
    pushw $0x0a
    pushl $load_root_cluster_msg_len
    pushl $load_root_cluster_msg
    call print_msg
    add $10, %sp
# >>>>>ROOT_CLUS_MEM_OFFSET<<<<<

#ifdef BOCHS_DEBUG_POINT
    xchgw %bx, %bx
#endif
# <<<<<开始读取FAT1至FAT1_MEM_OFFSET,如果FAT1占用超过FAT1_LOAD_SIZE扇区也只读FAT1_LOAD_SIZE个扇区>>>>>
    movl FATSz32(,1), %ebx
    cmpl $FAT1_LOAD_SIZE, %ebx
    jb 12f # >>>12 无符号小于 
    movw $FAT1_LOAD_SIZE, %bx
    
12: # 12<<<
    xorl %eax, %eax
    movw RsvdSecCnt(,1), %ax
    pushw %bx
    pushl %eax
    pushl $0
    pushl $FAT1_MEM_OFFSET
    call load_system_to_memory48
    add $14, %sp
    cmp $0, %eax
    jnz 90f # 90<<<
    pushw $0x0a
    pushl $load_fat1_msg_len
    pushl $load_fat1_msg
    call print_msg
    add $10, %sp
# >>>>>FAT1_MEM_OFFSET<<<<<

# 读取之后查找Enjoy.bin,每个目录项32个字节，总共比较 2*1024*1024/32=0x8000次，没找到继续读取磁盘
    xorw %cx, %cx
    movb SecPerClus(,1), %cl
    movw $0x10, %ax # 512/32=16
    mulw %cx
    shll $16, %edx
    movw %ax, %dx
    movl $ROOT_CLUS_MEM_OFFSET, %esi

13: # >>>13
    call find_enjoy_name
    cmpw $1, %ax # ax为1则找到,结果放到%ecx
    jz 14f # 14<<<
    addl $32, %esi
    decl %edx
    jg 13b # 13<<<
    jmp 87f # 87<<< 1M之内未找到，则报错


14: # >>>14
#找到了
    pushl %esi
    pushw $0x0a
    pushl $i_found_kernel_bin_msg_len
    pushl $i_found_kernel_bin_msg
    call print_msg
    add $10, %sp
    popl %esi
    movw 0x14(%esi), %dx
    shl $16, %edx
    movw 0x1a(%esi), %dx
    movl %edx, kernel_start_sec(,1) # 开始簇号
    movl 0x1c(%esi), %ebx
    movl %ebx, kernel_size(,1) # 内核大小

# 开始循环读取kernel
15: # >>>15
# 入参是kernel_start_sec[簇号],出参是eax[偏移]
    call get_clus_offset

    xor %cx, %cx
    movb SecPerClus(,1), %cl
    pushw %cx
    pushl %eax
    pushl $0
    pushl system_base_addr(,1)
    call load_system_to_memory48
    add $14, %sp
    cmp $0, %eax
    jnz 91f # 91<<<

    movl $FAT1_MEM_OFFSET, %esi
    movl kernel_start_sec(,1), %ebx
    movl (%esi, %ebx, 4), %eax
    cmpl $0x0fffffff, %eax
    jz 16f # 16<<<

    mov %eax, kernel_start_sec(,1)
    xor %eax, %eax
    xor %ebx, %ebx
    movb SecPerClus(,1), %al
    movw BytesPerClus(,1), %bx
    mull %ebx
    addl %eax, system_base_addr(,1)
    jmp 15b # 15<<<
 16: # >>>16
    pushw $0x0a
    pushl $load_system_msg_len
    pushl $load_system_msg
    call print_msg
    add $10, %sp


# 检查是否支持VBE
    call get_vesa_info_block
    jc 84f # 84<<<

    # after getting VesaInfoBlock:
	pushw  VideoModesSegment(,1)
	pop %es
    mov VideoModesOffset(,1), %si	#	获取VESA模式列表地址

17: # >>>17
	mov $VesaModeInfoBlockBuffer, %di
	mov %es:(%si), %cx							#	获取第一个模式
	cmp $0xffff, %cx							#	VESA模式列表最后一个？
	je 85f # 85<<<

# 获取mode信息
    call get_vesa_mode_info
    cmpw $2, %ax
    jz 83

    add $2, %si
    cmp $0, %ax
    jne 17b # 17<<<


# 将vesa mode info 搬运至VESA_MODE_INFO_ADDR存放，以备给内核用
    sub $2, %si #多加一次
    xorl %edi, %edi
    mov %si, %di # 备份si当前模式的地址，后面会用到
    xor %ecx, %ecx
    movw $VESA_MODE_INFO_DS, %bx
    shll $4, %ebx
    movw $VESA_MODE_INFO_ADDR, %bx
18: # >>>18
    cmp $256, %ecx
    jae 19f # 19<<<
    movl VesaModeInfoBlockBuffer(%ecx), %esi
    movl %esi, (%ebx, %ecx)
    add $4, %ecx
    jmp 18b # 18<<<

19: # >>>19
# 设置SVGA模式（VESA VBE）
    mov $0x4f02, %ax
    movw %es:(%di), %bx
    or $0x4000,  %bx
    int $0x10


    cmp $0x004f, %ax
    jnz 83f # 83<<<
# 跳转开启保护模式模块
    jmp start_enter_protect
    
80: # >>>80
    pushw $0x0a
    pushl $no_cpuid_msg_len
    pushl $no_cpuid_msg
    call print_msg
    add $10, %sp
    jmp 99f
81: # >>>81
    pushw $0x0a
    pushl $memory_info_fail_msg_len
    pushl $memory_info_fail_msg
    call print_msg
    add $10, %sp
    jmp 99f
82: # >>>82
    pushw $0x0a
    pushl %ecx
    pushl %esi
    call print_msg
    add $10, %sp
    jmp 99f
83: # >>>83
    pushw $0x0a
    pushl $svga_mode_fail_msg_len
    pushl $svga_mode_fail_msg
    call print_msg
    add $10, %sp
    jmp 99f
84: # >>>84
    pushw $0x0a
    pushl $get_vesa_info_failed_msg_len
    pushl $get_vesa_info_failed_msg
    call print_msg
    add $10, %sp
    jmp 99f
85: # >>>85
    pushw $0x0a
    pushl $not_support_svga_msg_len
    pushl $not_support_svga_msg
    call print_msg
    add $10, %sp
    jmp 99f
86: # >>>86
    pushw $0x0a
    pushl $not_boot_sec_msg_len
    pushl $not_boot_sec_msg
    call print_msg
    add $10, %sp
    jmp 99f
87: # >>>87
    pushw $0x0a
    pushl $not_found_kernel_msg_len
    pushl $not_found_kernel_msg
    call print_msg
    add $10, %sp
    jmp 99f
88: # >>>88
    pushw $0x0a
    pushl $not_supported_lba48_msg_len
    pushl $not_supported_lba48_msg
    call print_msg
    add $10, %sp
    jmp 99f
89: # >>>89
    pushw $0x0a
    pushl $error_root_cluster_load_msg_len
    pushl $error_root_cluster_load_msg
    call print_msg
    add $10, %sp
    jmp 99f
90: # >>>90
    pushw $0x0a
    pushl $error_fat1_load_msg_len
    pushl $error_fat1_load_msg
    call print_msg
    add $10, %sp
    jmp 99f
91: # >>>91
    pushw $0x0a
    pushl $error_load_kernel_msg_len
    pushl $error_load_kernel_msg
    call print_msg
    add $10, %sp
    jmp 99f


 99:
    hlt
    jmp 99b
# ------------------开启保护模式-----------------
# 0. 关中断
# 1. 打开A20
# 2. 加载gdt
# 3. 将cr0的pe位置1
start_enter_protect:
# -----------------关中断---------------------
    cli

# -----------------打开A20---------------------
    in $0x92, %al
    or $0b00000010, %al
    outb %al, $0x92

# -----------------加载gdt---------------------
    data32 lgdt GdtPtr32(,1)

# -----------------cr0的pe位置1----------------
    mov %cr0, %eax
    or $0x00000001, %eax
    mov %eax, %cr0

# 刷新流水线
    pushw $SelectorCode
    pushw $enter_protect_mode
    retf




.text 2
BEGIN_FUNC_STATIC(find_enjoy_name) # %esi,%edx不能修改，如果找到可以将结果放到%edx
    pushl %edx
    mov  $0, %eax
    movl $kernel_name_str_len, %ecx
    movl $kernel_name_str, %edi

1:
    cmpl %ecx, %eax
    jz 8f
    movb (%edi, %eax), %bl
    cmpb (%esi,%eax), %bl
    jnz 5f
    incl %eax
    jmp 1b

5: # 没找到
    movw $0, %ax
    jmp 9f
8: # 找到了
    movw $1, %ax
9:
    pop %edx
    ret
END_FUNC_STATIC(find_enjoy_name)

# 根据簇号获取偏移位置（单位：扇区）,入参是kernel_start_sec[扇区号],出参是eax[偏移]
BEGIN_FUNC_STATIC(get_clus_offset)
    movl kernel_start_sec(,1), %eax
    sub $2, %eax
    mull cluster_size(,1)
    addl root_clus_offset(,1), %eax
    xor %edx, %edx
    mov $512, %ecx
    divl %ecx
    ret
END_FUNC_STATIC(get_clus_offset)

BEGIN_FUNC_STATIC(enter_big_real_mode)
# 关中断
    cli
# 开A20
    in $0x92, %al
    or $0x02, %al
    out %al, $0x92

# 加载gdt
    data32 lgdt GdtPtr32(,1)

    
# 打开 CR0 Bit 0, 也就是 protected mode
    mov %cr0, %eax
    or $0x01, %eax
    mov %eax, %cr0
    
# 加载段选择子
    mov $SelectorData, %ax
    mov %ax, %fs
    mov $SelectorVideo, %ax
    mov %ax, %gs

# 关闭 CR0 Bit 0, 也就是 protected mode
    mov %cr0, %eax
    and $0xfe, %al
    mov %eax, %cr0

# 关中断
    sti
    ret
END_FUNC_STATIC(enter_big_real_mode)

BEGIN_FUNC_STATIC(get_vesa_mode_info)
    mov $0x4f01, %ax
    int $0x10
    cmp $0x004f, %ax
    jne 1f

    cmpw $SCREEN_RESOLUTION_X, width(,1) 
    jne 2f
    cmpw $SCREEN_RESOLUTION_H, height(,1)
    jne 2f
    cmpb $32, bpp(,1)
    jne 2f
    mov $0, %ax
    jmp 3f
1:
    mov $2, %ax
    jmp 3f
2:
    mov $1, %ax
3:
    ret
END_FUNC_STATIC(get_vesa_mode_info)

BEGIN_FUNC_STATIC(get_vesa_info_block)
    mov $VesaInfoBlockBuffer, %di
    mov $0x4f00, %ax
    int $0x10
    cmp $0x004f, %ax
    jne 1f
    clc
    ret
1:
    stc
    ret
END_FUNC_STATIC(get_vesa_info_block)

.text 3
# 获取光标的位置 dh-行,dl-列
BEGIN_FUNC_STATIC(init_print)
    mov $0x03, %ah
    mov $0x00, %bh #页号
    int $0x10
    movzbw %dh, %ax
    movw %ax, screen_h(,1)
    movzbw %dl, %ax
    movw %ax, screen_l(,1)
    ret
END_FUNC_STATIC(init_print)

# 打印退格
BEGIN_FUNC_STATIC(print_backsapce)
    ret
END_FUNC_STATIC(print_backsapce)
# 打印水平制表符
BEGIN_FUNC_STATIC(print_tab)
    ret
END_FUNC_STATIC(print_tab)
# 打印换行
BEGIN_FUNC_STATIC(print_newline)
    incw screen_h(,1)
    cmpw $25, screen_h(,1)
    jnz 1f
    movw $0, screen_h(,1)
1:
    movw $0, screen_l(,1)
    ret
END_FUNC_STATIC(print_newline)

# 打印回车
BEGIN_FUNC_STATIC(print_carriage)
    ret
END_FUNC_STATIC(print_carriage)
# 打印普通字符
BEGIN_FUNC_STATIC(print_char)
    pushl %eax
    movl $80, %eax
    mulw screen_h(,1)
    add screen_l(,1), %ax
    lea (%eax, %eax), %eax # 一个字符占2个字节
    addl $VGA_BASE_ADDR, %eax
    mov %eax, %edi
    pop %eax

    movw %ax, (%edi)
    ret
END_FUNC_STATIC(print_char)

# 打印字符
BEGIN_FUNC_STATIC(print_someone)
    cmp $0x08, %al # \b
    jnz 1f
    call print_backsapce
    ret
1:
    cmp $0x09, %al # \t
    jnz 2f
    call print_tab
    ret
2:
    cmp $0x0a, %al # \n
    jnz 3f
    call print_newline
    ret
3:
    cmp $0x0d, %al # \r
    jnz 4f
    call print_carriage
    ret
4:
    
    call print_char

    incw screen_l(,1)
    cmpw $80, screen_l(,1)
    jnz 6f
    incw screen_h(,1)
    cmpw $25, screen_h(,1)
    jnz 6f
    movw $0, screen_l(,1)
    movw $0, screen_h(,1)

6:
    ret
END_FUNC_STATIC(print_someone)

#第一个参数：字符串地址      4+4
#第二个参数：字符串长度      8+4
#第三个参数：字符串颜色（前/后）  12+2
BEGIN_FUNC_STATIC(print_msg)
    push %bp
    mov %sp, %bp

    xor %ecx, %ecx
    xor %eax, %eax
    movl 4(%bp), %esi
    movb 12(%bp), %ah
 1:
    cmpl %ecx, 8(%bp)
    jz 5f

    mov (%esi), %al
    call print_someone

    inc %ecx
    incl %esi
    jmp 1b
5:
    mov %bp, %sp
    pop %bp
    ret
END_FUNC_STATIC(print_msg)


.text 5
BEGIN_FUNC_STATIC(get_memory_info)
    mov $MEMORY_INFO_DS, %ebx
    push %es
    mov %bx, %es
    movw $0, %es:(MEMORY_INFO_COUNT)
    xor %ebx, %ebx
    mov $MEMORY_INFO_ADDR, %di
    mov $20, %ecx
    mov $0x534d4150, %edx
1:
    mov $0xe820, %eax
    int $0x15
    jc 3f
    incl %es:(MEMORY_INFO_COUNT)
    cmp $0, %ebx
    jz 2f
    add $20, %di
    jmp 1b
2:
    pop %es
    mov $0, %eax
    ret
3:
    pop %es
    mov $1, %eax
    ret
END_FUNC_STATIC(get_memory_info)

.text 6
#判断是否支持CPUID
BEGIN_FUNC_STATIC(check_cpuid)
# Check if CPUID is supported by attempting to flip the ID bit (bit 21) in
# the FLAGS register. If we can flip it, CPUID is available.
 
# Copy FLAGS in to EAX via stack
    pushf
    pop %eax
 
# Copy to ECX as well for comparing later on
    mov %eax, %ecx 
 
# Flip the ID bit
    xor $1 << 21, %eax
 
# Copy EAX to FLAGS via the stack
    push %eax
    popf
 
# Copy FLAGS back to EAX (with the flipped bit if CPUID is supported)
    pushf
    pop %eax
 
# Restore FLAGS from the old version stored in ECX (i.e. flipping the ID bit
# back if it was ever flipped).
    push %ecx
    popf
 
# Compare EAX and ECX. If they are equal then that means the bit wasn't
# flipped, and CPUID isn't supported.
    xor %ecx, %eax
    jz 1f
    ret

1:
    stc
    ret
END_FUNC_STATIC(check_cpuid)

/*
To use the IDENTIFY command, select a target drive by sending 0xA0 for the master drive,
or 0xB0 for the slave, to the "drive select" IO port. On the Primary bus, this would be port 0x1F6.
Then set the Sectorcount, LBAlo, LBAmid, and LBAhi IO ports to 0 (port 0x1F2 to 0x1F5).
Then send the IDENTIFY command (0xEC) to the Command IO port (0x1F7). Then read the Status port (0x1F7) again.
If the value read is 0, the drive does not exist. For any other value: 
poll the Status port (0x1F7) until bit 7 (BSY, value = 0x80) clears. 
Because of some ATAPI drives that do not follow spec, at this point you need to check the LBAmid and LBAhi ports (0x1F4 and 0x1F5) to see if they are non-zero. 
If so, the drive is not ATA, and you should stop polling. Otherwise, 
continue polling one of the Status ports until bit 3 (DRQ, value = 8) sets, or until bit 0 (ERR, value = 1) sets.

At that point, if ERR is clear, the data is ready to read from the Data port (0x1F0). 
Read 256 16-bit values, and store them.
*/
.text 7
# 入栈顺序从右向左
# 第一个参数 将磁盘数据写入的内存地址 bx 4byte 10+4
# 第二个参数 起始LBA扇区号     eax 4byte 6+4   
# 第三个参数 读取的扇区总数     cx 2byte 4+2 只有低字节有效
# 返回地址               2byte 2+2 (不是参数)
# 寄存器bp               2byte 0+2(bp值)
# load_system_to_memory28(int *addr, int start_sec, short total)
BEGIN_FUNC_STATIC(load_system_to_memory28)
    push %bp
    mov %sp, %bp

# 读写硬盘
# 第0步 清空 0x1f1 端口
    mov $0x1f1, %dx
    mov $0, %al
    out %al, %dx

# 第1步 设置要读取的扇区数
    mov $0x1f2, %dx
    movb 4(%bp), %al
    out %al, %dx  #读取的扇区数

    mov 6(%bp), %eax # 恢复ax
# 第2步 将 LBA 地址存入 0x1f3 ~ 0x1f6
    # LBA 地址7~0位写入端口 0x1f3
    mov $0x1f3, %dx
    out %al, %dx

    # LBA 地址 15~8 位写入端口 0x1f4
    shr $0x08, %eax
    mov $0x1f4, %dx
    out %al, %dx

    # LBA 地址 23~16 位写入端口 0x1f5
    shr $0x08, %eax
    mov $0x1f5, %dx
    out %al, %dx

    shr $0x08, %eax
    and $0x0f, %al # lba 第24~27位
    or $0xe0, %al   # 设置7~4位为1110, 表示lba模式
    mov $0x1f6, %dx
    out %al, %dx



# 第3步 向 0x1f7 端口写入读命令， 0x20

    mov $0x1f7, %dx
    mov $(ATA_CMD_READ_PIO), %al
    out %al, %dx

# 第4步 检查硬盘状态
1:
# 同一端口，写时表示写入命令字，读时表示读入硬盘状态
    nop
    in %dx, %al
    pushw %ax #备份 ax
    and $0x01, %al # 第0位为1表示硬盘控制器报错
    cmp $0x01, %al
    jz 3f
    popw %ax
    and $0x88, %al # 第3位为1表示硬盘控制器已准备好数据传输  第7位为1表示硬盘忙
    cmp $0x08, %al
    jnz 1b  # 若未准备好，继续等待

# 第5步 从0x1f0端口读数据
    xor %eax, %eax
    movb 4(%bp), %al
    mov $(512/2), %dx
    mul %dx
    mov %eax, %ecx   # di 为要读取的扇区数，一个扇区有 512 字节，每次读入一个字
           #  共需 di*512/2 次，所以 di*256

    mov 10(%bp), %ebx # 将写入的内存地址放入bx寄存器
    mov $0x1f0, %dx
2:
    in %dx, %ax
    movw %ax, (%ebx)
    add $2, %bx
    decl %ecx
    cmpl $0, %ecx
    jnz 2b
    mov $0x0, %ax
    mov %bp, %sp
    pop %bp
    ret
3:
    mov $0x01, %ax
    mov %bp, %sp
    pop %bp
    ret
END_FUNC_STATIC(load_system_to_memory28)

/*
Reading sectors using 48 bit PIO is very similar to the 28 bit method:

(Notes: A sector count of 0 means 65536 sectors = 32MB. Try not to send bytes to the same IO port twice in a row. 
Doing so is much slower than doing two outb() commands to different IO ports. The important thing is that the high byte of the sector count,
 features and LBA bytes 4, 5, & 6 go to their respective ports before the low bytes.)

Assume you have a sectorcount uint16_t and a 6 byte LBA value. Mentally number the LBA bytes as 1 to 6, from low to high. 
Send the 2 byte sector count to port 0x1F2 (high byte first), and the six LBA byte pairs to ports 0x1F3 through 0x1F5 in some appropriate order.

An example:

Send 0x40 for the "master" or 0x50 for the "slave" to port 0x1F6: outb(0x1F6, 0x40 | (slavebit << 4))
outb (0x1F2, sectorcount high byte)
outb (0x1F3, LBA4)
outb (0x1F4, LBA5)
outb (0x1F5, LBA6)
outb (0x1F2, sectorcount low byte)
outb (0x1F3, LBA1)
outb (0x1F4, LBA2)
outb (0x1F5, LBA3)
Send the "READ SECTORS EXT" command (0x24) to port 0x1F7: outb(0x1F7, 0x24)
Note on the "magic bits" sent to port 0x1f6: Bit 6 (value = 0x40) is the LBA bit. This must be set for either LBA28 or LBA48 transfers. It must be clear for CHS transfers. Any drive that can support LBA48 will ignore all other bits on this port for an LBA48 command. You can set them if it will make your code cleaner (to use the same magic bits as LBA28).

To write sectors in 48 bit PIO mode, send command "WRITE SECTORS EXT" (0x34), instead. (As before, do not use REP OUTSW when writing.) And remember to do a Cache Flush after each write command completes.

After the command byte is sent, transfer each sector of data in exactly the same way as for a 28 bit PIO Read/Write command.
*/
# 下面是参数在栈的偏移位置
# 第四个参数 读取的扇区总数            2byte 16+2
# 第三个参数 起始LBA扇区号(低4位)      4byte 12+4
# 第二个参数 起始LBA扇区号(高4位)      4byte 8+4
# 第一个参数 将磁盘数据写入的内存地址    4byte 4+4
# 返回地址               2byte 2+2 (不是参数)
# 寄存器bp               2byte 0+2(bp值)
# load_system_to_memory48(int addr, int start_sec_h, int start_sec_l, short total）
BEGIN_FUNC_STATIC(load_system_to_memory48)
    pushw %bp
    movw %sp, %bp
    
    xorl %edx, %edx
    movw $IO_BASE_PRIMARY, %dx # 0x1f0
# 读取扇区数
    movw 16(%bp), %cx
    orb $2, %dl # sectorcount high byte 0x1f2
    movb %ch, %al
    out %al,  %dx
# 起始LBA扇区号(低4位)
    movl 12(%bp), %eax
    movl %eax, %ebx # 备份LBA低32位
    bswap %eax
    incl %edx # LBA4 0x1f3
    out %al,  %dx

    movl 8(%bp), %eax
    incl %edx # LBA5 0x1f4
    out %al,  %dx

    movb %ah, %al
    incl %edx # LBA6 0x1f5
    out %al,  %dx

    movb %cl, %al
    subb $3, %dl # sectorcount low byte 0x1f2
    out %al,  %dx
    
    movb %bl, %al
    incl %edx # LBA1 0x1f3
    out %al,  %dx

    movb %bh, %al
    incl %edx # LBA2 0x1f4
    out %al,  %dx

    shrl $16, %ebx
    movb %bl, %al
    incl %edx # LBA3 0x1f5
    out %al,  %dx

    movb $0x40, %al
    incl %edx # select master drive 0x1f6
    out %al, %dx

    movb $0x24, %al # read ext
    incl %edx # command 0x1f7
    out %al, %dx


    movw %cx, %bx # 扇区计数
    movl 12(%bp), %esi # lba偏移
    movl 4(%bp), %edi
# 忽略前4个状态读取的错误位  (即在ERR上实现400ns延迟)仅等待BSY复位和DRQ置位
	movl $4, %ecx
1:
	in %dx, %al		#  获取状态字节
	test $ATA_SR_BSY, %al		# BSY位被置位?
	jne 2f
	test $ATA_SR_DRQ, %al		# DRQ位被置位?
	jne 4f
2:
	dec %ecx
	jg 1b

# 需要再等一会儿 -- 循环直到 BSY 复位或 ERR 置位（如果 ERR 置位，则错误退出）
3:
	in %dx, %al		# 获取状态字节
	test $ATA_SR_BSY, %al		# BSY位被置位?
	jne  3b	# 如果置位了 BSY，则所有其他标志都毫无意义
	test $(ATA_SR_DF|ATA_SR_ERR), %al		# ERR 或 DF 被置位?
	jne  6f

4: # 如果 BSY 和 ERR 被复位，则必须设置 DRQ -- 去读取数据
    subb $7, %dl		# read from data port (ie. 0x1f0)
	mov $256, %cx
5:
    in %dx, %ax
    movw %ax, (%edi)
    addl $2, %edi
    decw %cx
    jg 5b

	orb $7, %dl		# 将 DX “指向” 状态寄存器
	in %dx, %al		# 延迟 400ns，允许驱动器设置 BSY 和 DRQ 的新值
	in %dx, %al
	in %dx, %al
	in %dx, %al

# 在每个 DRQ 数据块之后，必须执行以下任一操作：
# 接收并确认 IRQ -- 或重新轮询状态端口

    incl %esi # 增加当前绝对LBA（溢出是可以的！）
    decl %ebx # 减少“要读取的扇区”计数
    jne 3b
    subw $7, %dx  # 将 dx “指向” 回到 IO基础端口，因此它保持不变
    testb $0x21, %al  # 测试最后状态 ERR 位
	je 7f
6:
    movw $drive_read_err_msg,  %si
    movl $drive_read_err_msg_len,  %ecx
    movl $1, %eax
    jmp 8f
    
7:
    movl $0, %eax
8:
    movw %bp, %sp
    popw %bp
    ret

END_FUNC_STATIC(load_system_to_memory48)



# 判断PRIMARY通道Master设备是否可用并关闭中断
BEGIN_FUNC_STATIC(get_disk_info)
    mov $ATA_IDENT_MASTER_SELECTOR, %al
    mov $(IO_BASE_PRIMARY+ATA_REG_HDDEVSEL),   %dx
    out %al, %dx

    mov $0,  %al
    mov $(IO_BASE_PRIMARY+ATA_REG_SECCOUNT0), %dx
    out %al,  %dx

    mov $(IO_BASE_PRIMARY+ATA_REG_LBA0), %dx
    out %al,  %dx

    mov $(IO_BASE_PRIMARY+ATA_REG_LBA1), %dx
    out %al,  %dx

    mov $(IO_BASE_PRIMARY+ATA_REG_LBA2), %dx
    out %al,  %dx

    mov $ATA_CMD_IDENTIFY, %al
    mov $(IO_BASE_PRIMARY+ATA_REG_STATUS),  %dx
    out %al,  %dx

21:
    mov $(IO_BASE_PRIMARY+ATA_REG_STATUS),  %dx
    in    %dx, %al
    cmp  $0,  %al
    jz 22f
    mov %al,  %cl

    mov $(IO_BASE_PRIMARY+ATA_REG_LBA1), %dx
    in    %dx, %al
    cmp $0, %al
    jnz 23f

    mov $(IO_BASE_PRIMARY+ATA_REG_LBA2), %dx
    in    %dx, %al
    cmp $0, %al
    jnz 23f

    mov  %cl, %al
    test $0x01, %al
    jnz 24f

    and $0x88, %al
    cmp $0x08, %al
    jnz 21b

    
    mov $(IO_BASE_PRIMARY+ATA_REG_DATA), %dx
    xorl %esi, %esi
    lea disk_info, %ebx
    mov $256, %cx
26: # 拷贝硬盘信息到disk_info结构体里
    in %dx, %ax
    movw %ax, (%ebx, %esi, 2)
    inc  %esi
    loop 26b

    # 禁用中断
    mov $0x02, %al
    mov $(CONTROL_BASE_PRIMARY),  %dx
    out %al,  %dx
    mov $0, %eax
    ret
22:
    mov $drive_not_exist_msg,  %esi
    mov $drive_not_exist_msg_len,  %ecx
    mov $1, %eax
    ret
23:
    mov $drive_not_ata_msg,  %esi
    mov $drive_not_ata_msg_len,  %ecx
    mov $1, %eax
    ret
24:
    mov $drive_read_err_msg,  %esi
    mov $drive_read_err_msg_len,  %ecx
    mov $1, %eax
    ret
END_FUNC_STATIC(get_disk_info)

BEGIN_FUNC_STATIC(check_disk_lba48)
    mov $83, %ecx
    lea disk_info, %esi
    movw (%esi, %ecx, 2), %ax
    test $0b10000000000,  %ax
    jz 1f
    mov $1, %ax
    ret
1:
    mov  $0,   %ax
    ret
END_FUNC_STATIC(check_disk_lba48)

BEGIN_FUNC_STATIC(get_max_offset_lba48)
    mov $103, %ecx
    lea disk_info, %esi
    movw (%esi, %ecx, 2), %dx
    shll $16, %edx
    decl %ecx
    movw (%esi, %ecx, 2), %dx
    decl %ecx
    movw (%esi, %ecx, 2), %ax
    shll $16, %eax
    decl %ecx
    movw (%esi, %ecx, 2), %ax
    
    ret
END_FUNC_STATIC(get_max_offset_lba48)




.text 8
.code32
enter_protect_mode:
    movl $SelectorData, %eax
    mov %eax, %ds
    mov %eax, %es
    mov %eax, %fs
    mov %eax, %ss
    movl $STACK_32_ADDR, %esp


# 是否支持64位模式
    call check_long_mode
    jc error_longmode


# 设置页表结构
    call setup_page64


# 开启PAE
    mov %cr4, %eax
    or $(1<<5), %eax
    mov %eax, %cr4

# 设置CR3
    mov $PML4_TMP_BASE_ADDR, %eax
    mov %eax, %cr3



# 设置IA32_EFER的LM位 开启长模式
    mov $0xc0000080, %ecx
    rdmsr
    or $(1<<8), %eax
    wrmsr

# 开启分页
    mov %cr0, %eax
    or $(1<<31), %eax
    mov %eax, %cr0

#  加载64位GDT
    lgdt GdtPtr64(,1)

# 刷新流水线
    pushl $SelectorCode64
    pushl $SYSTEM_BASE_ADDR
    retf

error_longmode:
    hlt

# 设置页表结构
BEGIN_FUNC_STATIC(setup_page64)

.intel_syntax noprefix
    mov edi, PML4_TMP_BASE_ADDR    # Set the destination index to 0x1000.
    xor eax, eax       # Nullify the A-register.
    mov ecx, 4096      # Set the C-register to 4096.
    rep stosd          # Clear the memory.

.att_syntax
    movl $(PDPT_TMP_BASE_ADDR|0x3), (PML4_TMP_BASE_ADDR+0x0*0x8)   #PML4[0  ]0x0000 0000 0000 0000
    movl $(PDPT_TMP_BASE_ADDR|0x3), (PML4_TMP_BASE_ADDR+0x100*0x8) #PML4[256]0xffff 8000 0000 0000

    movl $(PDT_TMP_BASE_ADDR|0x3),  (PDPT_TMP_BASE_ADDR+0x0*0x8)               #PDPT[0]

    movl $(0x000000|0x83), (PDT_TMP_BASE_ADDR+0x0*0x8)                     #PD[0]
    movl $(0x200000|0x83), (PDT_TMP_BASE_ADDR+0x1*0x8)                     #PD[1]
    movl $(0x400000|0x83), (PDT_TMP_BASE_ADDR+0x2*0x8)                     #PD[2]
    movl $(0x600000|0x83), (PDT_TMP_BASE_ADDR+0x3*0x8)                     #PD[3]
    movl $(0x800000|0x83), (PDT_TMP_BASE_ADDR+0x4*0x8)                     #PD[4]

    ret
END_FUNC_STATIC(setup_page64)


# 检查是否支持64位CPU模式
BEGIN_FUNC_STATIC(check_long_mode)
    mov $0x80000001, %eax    # Set the A-register to 0x80000001.
    cpuid          # CPU identification.
    test $1 << 29, %edx      # Test if the LM-bit, which is bit 29, is set in the D-register.
    jz 1f
    ret
1:
    stc
    ret
END_FUNC_STATIC(check_long_mode)


.section .data


# ----------------------32位描述符------------------    
.align 8
.data 1
GdtBase:
GdtNull:    .quad 0
GdtCode:    .quad 0x00cf9a000000ffff
GdtData:    .quad 0x00cf92000000ffff # 32 data
GdtVideo:   .quad 0x004f920b80007fff

    .equiv GdtLen, .-GdtBase
GdtPtr32:
    GdtSize: .dc.w GdtLen-1
    BaseAddr: .dc.l GdtBase

    .equiv SelectorCode,   0x08
    .equiv SelectorData,   0x10
    .equiv SelectorVideo,  0x18

# ------------------------64位描述符------------------------
.align 8
.data 2
GdtBase64:
GdtNull64:     .quad 0
SystemCode64:  .quad 0x00209a0000000000
SystemData64:  .quad 0x0020920000000000


    .equiv GdtLen64, .-GdtBase64
GdtPtr64:
    GdtSize64: .dc.w GdtLen64-1
    BaseAddr64: .dc.l GdtBase64
                .dc.l 0x0

    .equiv SelectorCode64,  0x08
    .equiv SelectorData64,  0x10

.data 3
VesaInfoBlockBuffer:
Signature:                  .ascii "VESA"         // == "VESA"
Version:                    .dc.w 0x0300          // == 0x0300 for VBE 3.0
OEMNamePtr:                 .dc.l 0
Capabilities:               .dc.l 0
VideoModesOffset:           .dc.w 0
VideoModesSegment:          .dc.w 0
CountOf64KBlocks:           .dc.w 0
OEMSoftwareRevision:        .dc.w 0
OEMVendorNamePtr:           .dc.l 0
OEMProductNamePtr:          .dc.l 0
OEMProductRevisionPtr:      .dc.l 0
Reserved:                   .rept 222
                            .dc.b 0
                            .endr
OEMData:                    .rept 256
                            .dc.b 0
                            .endr

.data 4
DosDBR:
JumpCode:                   .dc.b 0,0,0 # 跳转指令
OEMName:                    .ascii "        " # 文件系统标志和版本号
BytesPerClus:               .dc.w  0x0 # 每扇区字节数
SecPerClus:                 .dc.b 0x0 # 每簇扇区数
RsvdSecCnt:                 .dc.w 0x0 # 保留扇区数，符合FAT1起始地址为38扇区
NumFATs:                    .dc.b 0x0 # FAT表个数
RootEntCnt:                 .dc.w 0x0 # FAT32必须等于0，FAT12/FAT16为根目录中目录的个数
TotSec16:                   .dc.w 0x0 # FAT32必须等于0，FAT12/FAT16为扇区总数
Media:                      .dc.b 0x0 # 哪种存储介质，0xF8标准值，可移动存储介质，常用的 0xF0
FATSz16:                    .dc.w 0x0 # FAT32必须为0，FAT12/FAT16为一个FAT 表所占的扇区数
SecPerTrk:                  .dc.w 0x0 # 每磁道扇区数，只对于有“特殊形状”（由磁头和柱面每 分割为若干磁道）的存储介质有效，63（0x00 3F）
NumHeads:                   .dc.w 0x0 # 磁头数，只对特殊的介质才有效
HiddSec:                    .dc.l 0x0 # EBR分区之前所隐藏的扇区数，8192（0x00 00 20 00），与MBR中地址0x1C6开始的4个字节数值相等
TotSec32:                   .dc.l 0x0 # 文件系统总扇区数
FATSz32:                    .dc.l 0x0 # 每个FAT表占用扇区数
ExtFlags:                   .dc.w 0x0 # 标记，此域FAT32 特有
FSVer:                      .dc.w 0x0 # FAT32版本号0.0，FAT32特有
RootClus:                   .dc.l 0x0 # 根目录所在第一个簇的簇号，2。（虽然在FAT32文件系统 下，根目录可以存放在数据区的任何位置，但是通常情况下还是起始于2号簇）
FSInfo:                     .dc.w 0x0 # FSINFO（文件系统信息扇区）扇区号1，该扇区为操作 系统提供关于空簇总数及下一可用簇的信息
BKBootSec:                  .dc.w 0x0 # 备份引导扇区的位置。备份引导扇区总是位于文件系统 的6号扇区
DBRReserved:                .dc.b 0,0,0,0,0,0,0,0,0,0,0,0 # 用于以后FAT 扩展使用
DrvNum:                     .dc.b 0 # 与FAT12/16 的定义相同，只不过两者位于启动扇区不同的位置而已
DBRReserved1:               .dc.b 0 # 与FAT12/16 的定义相同，只不过两者位于启动扇区不同的位置而已 
BootSig:                    .dc.b 0 # 扩展引导标志
VolID:                      .dc.l 0 # 卷序列号。通常为一个随机值
VolName:                    .ascii "           " # 卷标（ASCII码），如果建立文件系统的时候指定了卷 标，会保存在此
FilSysType:                 .ascii "        " # 文件系统格式的ASCII码，FAT32
Reserved0:                  .rept 350
                            .dc.b 0
                            .endr
UniqueDiskID:               .dc.l 0
Reserved1:                  .dc.w 0
DriveAttributes0:           .dc.b 0
CHSAddressStart0:           .rept 3
                            .dc.b 0
                            .endr
PartitionType0:             .dc.b 0
CHSAddressEnd0:             .rept 3
                            .dc.b 0
                            .endr
LBAAddressStart0:           .dc.l 0
NumOfSectorPartition0:      .dc.l 0
DriveAttributes1:           .dc.b 0
CHSAddressStart1:           .rept 3
                            .dc.b 0
                            .endr
PartitionType1:             .dc.b 0
CHSAddressEnd1:             .rept 3
                            .dc.b 0
                            .endr
LBAAddressStart1:           .dc.l 0
NumOfSectorPartition1:      .dc.l 0
DriveAttributes2:           .dc.b 0
CHSAddressStart2:           .rept 3
                            .dc.b 0
                            .endr
PartitionType2:             .dc.b 0
CHSAddressEnd2:             .rept 3
                            .dc.b 0
                            .endr
LBAAddressStart2:           .dc.l 0
NumOfSectorPartition2:      .dc.l 0
DriveAttributes3:           .dc.b 0
CHSAddressStart3:           .rept 3
                            .dc.b 0
                            .endr
PartitionType3:             .dc.b 0
CHSAddressEnd3:             .rept 3
                            .dc.b 0
                            .endr
LBAAddressStart3:           .dc.l 0
NumOfSectorPartition3:      .dc.l 0
MagicNumber:                .dc.w 0


.data 5
root_clus_offset: .dc.l 0x0 # 根目录偏移字节数
cluster_size:     .dc.l 0x0 # 每个簇占用多少个字节
fat1_offset:      .dc.l 0x0 # FAT1偏移字节数
is_support_lba48: .dc.b 0x0 # 0-不支持 1-支持
kernel_size:      .dc.l 0x0
kernel_start_sec: .dc.l 0x0
system_base_addr: .dc.l SYSTEM_BASE_ADDR


.data 10
kernel_name_str:
    .ascii KERNEL_NAME
    .equiv kernel_name_str_len, .-kernel_name_str
no_cpuid_msg:
    .ascii "Not supported CPUID\n"
    .equiv no_cpuid_msg_len, .-no_cpuid_msg
no_long_mode_msg:
    .ascii "Not supported Long Mode\n"
    .equiv lno_long_mode_msg_len, .-no_long_mode_msg
big_real_mode_msg:
    .ascii "Enter big real mode success\n"
    .equiv big_real_mode_msg_len, .-big_real_mode_msg
memory_info_msg:
    .ascii "Init memory information success\n"
    .equiv memory_info_msg_len, .-memory_info_msg
get_disk_info_msg:
    .ascii "Get disk information success\n"
    .equiv get_disk_info_msg_len, .-get_disk_info_msg
supported_lba48_msg:
    .ascii "Supported LBA48 mode\n"
    .equiv supported_lba48_msg_len, .-supported_lba48_msg
load_root_cluster_msg:
    .ascii "Load root cluster to memory success\n"
    .equiv load_root_cluster_msg_len, .-load_root_cluster_msg
load_fat1_msg:
    .ascii "Load FAT1 to memory success\n"
    .equiv load_fat1_msg_len, .-load_fat1_msg
i_found_kernel_bin_msg:
    .ascii "Founded Enjoy.bin\n"
    .equiv i_found_kernel_bin_msg_len, .-i_found_kernel_bin_msg
memory_info_fail_msg:
    .ascii "Init memory information failed\n"
    .equiv memory_info_fail_msg_len, .-memory_info_fail_msg
supported_cupid_msg:
    .ascii "Supported CPUID\n"
    .equiv supported_cupid_msg_len, .-supported_cupid_msg
loading_system_msg:
    .ascii "#"
    .equiv loading_system_msg_len, .-loading_system_msg
load_system_msg:
    .ascii "Load system success\n"
    .equiv load_system_msg_len, .-load_system_msg
load_system_fail_msg:
    .ascii "Load system failed\n"
    .equiv load_system_fail_msg_len, .-load_system_fail_msg
drive_not_exist_msg:
    .ascii "Driv does not exist\n"
    .equiv drive_not_exist_msg_len, .-drive_not_exist_msg
drive_not_ata_msg:
    .ascii "Driv is not ATA device\n"
    .equiv drive_not_ata_msg_len, .-drive_not_ata_msg
drive_read_err_msg:
    .ascii "Driv read error\n"
    .equiv drive_read_err_msg_len, .-drive_read_err_msg
svga_mode_fail_msg:
    .ascii "Set SVGA mode failed\n"
    .equiv svga_mode_fail_msg_len, .-svga_mode_fail_msg
get_vesa_info_failed_msg:
    .ascii "Get VESA Information failed\n"
    .equiv get_vesa_info_failed_msg_len, .-get_vesa_info_failed_msg
not_support_svga_msg:
    .ascii SCREEN_RESOLUTION_STR(SCREEN_RESOLUTION_X, SCREEN_RESOLUTION_H)
    .equiv not_support_svga_msg_len, .-not_support_svga_msg
not_boot_sec_msg:
    .ascii "No boot partition\n"
    .equiv not_boot_sec_msg_len, .-not_boot_sec_msg
not_found_kernel_msg:
    .ascii "Not found kernel image\n"
    .equiv not_found_kernel_msg_len, .-not_found_kernel_msg
not_supported_lba48_msg:
    .ascii "Not supported LBA48\n"
    .equiv not_supported_lba48_msg_len, .-not_supported_lba48_msg
error_root_cluster_load_msg:
    .ascii "Load root cluster ted\n"
    .equiv error_root_cluster_load_msg_len, .-error_root_cluster_load_msg
error_fat1_load_msg:
    .ascii "Load FAT1 to memory failed\n"
    .equiv error_fat1_load_msg_len, .-error_fat1_load_msg
error_load_kernel_msg:
    .ascii "Load system to memory failed\n"
    .equiv error_load_kernel_msg_len, .-error_load_kernel_msg



.section .bss
screen_l: .dc.w 0
screen_h: .dc.w 0
#  uint16_t 83: Bit 10 is set if the drive supports LBA48 mode.
disk_info:
    .rept   256
    .dc.w 0
    .endr
VesaModeInfoBlockBuffer:
     attributes:                 .dc.w 0         // 废弃, 你可能只对第7bit感兴趣, 它决定了是否支持 ‘linear frame buffer’.
     window_a:                   .dc.b 0         // 废弃
     window_b:                   .dc.b 0         // 废弃
     granularity:                .dc.w 0         // deprecated, used while calculating bank numbers
     window_size:                .dc.w 0 
     segment_a:                  .dc.w 0 
     segment_b:                  .dc.w 0 
     win_func_ptr:               .dc.l 0         // deprecated, used to switch banks from protected mode without returning to real mode
     pitch:                      .dc.w 0         // number of bytes per horizontal line
     width:                      .dc.w 0         // 屏幕宽有width个像素
     height:                     .dc.w 0         // 屏幕高有height个像素
     w_char:                     .dc.b 0         // 未使用
     y_char:                     .dc.b 0
     planes:                     .dc.b 0 
     bpp:                        .dc.b 0         // 当前模式一个像素占多少bit
     banks:                      .dc.b 0         // deprecated, total number of banks in this mode
     memory_model:               .dc.b 0 
     bank_size:                  .dc.b 0         // deprecated, size of a bank, almost always 64 KB but may be 16 KB...
     image_pages:                .dc.b 0 
     reserved0:                  .dc.b 0 
     red_mask:                   .dc.b 0 
     red_position:               .dc.b 0 
     green_mask:                 .dc.b 0 
     green_position:             .dc.b 0 
     blue_mask:                  .dc.b 0 
     blue_position:              .dc.b 0 
     reserved_mask:              .dc.b 0 
     reserved_position:          .dc.b 0 
     direct_color_attributes:    .dc.b 0 
     framebuffer:                .dc.l 0         // ‘linear frame buffer’的物理地址, 通过写对应的地址可以实现屏幕操作（显示各种颜色）
     off_screen_mem_off:         .dc.l 0 
     off_screen_mem_size:        .dc.w 0         // 帧缓冲内存大小但未显示在屏幕上
     reserved1:
                                 .rept 206
                                 .dc.b 0
                                 .endr

